﻿using Xejen.Ipc.Framework.Flows;
using Microsoft.Extensions.Logging;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Xejen.Ipc.Framework.Steps
{
    /// <inheritdoc cref="IStep{TData}"/>
    /// <typeparam name="TData"><inheritdoc cref="IFlow{TData}" path="/typeparam[@name='TData']"/></typeparam>
    /// <typeparam name="TStepBody"></typeparam>
    internal class Step<TData, TStepBody> : IStep<TData>
        where TData : new()
        where TStepBody : IStepBody<TData>, new()
    {
        private readonly ILogger _logger;
        private readonly StepInstance<TData> _stepInstance;
        private readonly IStepBody<TData> _stepBody;
        private readonly Stopwatch _stopwatch = new Stopwatch();

        /// <inheritdoc/>
        public int Id { get; set; }

        /// <inheritdoc/>
        public string Name { get; set; }

        /// <inheritdoc/>
        public event EventHandler<StepEventArgs<TData>> Started;
        /// <inheritdoc/>
        public event EventHandler<StepEventArgs<TData>> Completed;
        /// <inheritdoc/>
        public event EventHandler<ErrorEventArgs<TData>> Error;

        /// <inheritdoc cref="Step{TData, TStepBody}"/>
        /// <param name="id"><inheritdoc cref="Id" path="/summary"/></param>
        /// <param name="name"><inheritdoc cref="Name" path="/summary"/></param>
        public Step(int id, string name)
        {
            Id = id;
            Name = name;

            _logger = HostRuntime.LoggerManager.CreateLogger<Step<TData, TStepBody>>();

            _stepBody = new TStepBody();
            _stepInstance = new StepInstance<TData>(this);
        }

        /// <inheritdoc/>
        public async Task ExecuteAsync(IFlowExecutionContext<TData> context, CancellationToken cancellationToken = default)
        {
            if (cancellationToken.IsCancellationRequested)
            {
                Debug.WriteLine($"步骤内的当前流程状态被取消，流程停止并返回");
                return;
            }
            if (context.Flow.Status == FlowStatus.Stopped)
            {
                Debug.WriteLine($"步骤内的当前流程状态变更为：{context.Flow.Status}，流程停止并返回");
                return;
            }
            while (context.Flow.Status == FlowStatus.Paused)
            {
                await Task.Delay(10);
                continue;
            }

            OnStarted(this, new StepEventArgs<TData>(_stepInstance));

            try
            {
                cancellationToken.ThrowIfCancellationRequested();

                _stepInstance.CycleEndTime = null;
                _stepInstance.CycleExecutionTime = null;

                _stepInstance.CycleStartTime = StandardTime.Now;
                _stopwatch.Restart();

                await _stepBody.ExecuteAsync(context);

                _stopwatch.Stop();
                _stepInstance.CycleEndTime = StandardTime.Now;
                _stepInstance.CycleExecutionTime = _stopwatch.Elapsed;
            }
            catch (OperationCanceledException exp)
            {
                OnError(this, new ErrorEventArgs<TData>(exp));
            }
            catch (Exception exp)
            {
                OnError(this, new ErrorEventArgs<TData>(exp));
            }
            finally
            {
                OnCompleted(this, new StepEventArgs<TData>(_stepInstance));
            }
        }

        /// <summary>
        /// 当步骤进入时触发
        /// </summary>
        protected virtual void OnStarted(object sender, StepEventArgs<TData> args)
        {
            Started?.Invoke(sender, args);
        }
        /// <summary>
        /// 当步骤出去时触发
        /// </summary>
        protected virtual void OnCompleted(object sender, StepEventArgs<TData> args)
        {
            Completed?.Invoke(sender, args);
        }
        /// <summary>
        /// 当发生错误时触发
        /// </summary>
        protected virtual void OnError(object sender, ErrorEventArgs<TData> args)
        {
            _logger.LogError(args.Exception, args.Exception.Message);
            Error?.Invoke(sender, args);
        }
    }
}
